#include <webx/route.h>
#include <http/HttpHelper.h>

class TraceModule : public webx::ProcessBase
{
protected:
	int process();
};

HTTP_WEBCGI(CGI_PRIVATE, "${filename}")
DEFINE_HTTP_CGI_EXPORT_FUNC(TraceModule)

static int maxLogCnt = 1000000;
static HttpServer* app = HttpServer::Instance();

class LoggerTimer : public WorkItem
{
	typedef long long timeus;

private:
	struct Logger
	{
		string msg;
		timeus ctime;
	};

	Mutex mtx;
	LogThread logfile;
	SmartBuffer buffer;
	multimap<string, Logger> logmap;

public:
	void run()
	{
		trySync();
	}
	bool init()
	{
		CHECK_FALSE_RETURN(buffer.malloc(8 * 1024 * 1024));

		string path = LogThread::Instance()->getLogPath() + string("/webapp");

		return logfile.init(path, LogThread::Instance()->getMaxFileSize(), true);
	}
	int pop()
	{
		int res = 0;
		Locker lk(mtx);

		if (logmap.empty()) return res;

		auto now = GetTime();
		auto check = [now](const Logger& log){
			long gap = now - log.ctime;
			return gap > 5 * 1000 * 1000;
		};

		for (auto it = logmap.begin(); it != logmap.end() && check(it->second); it = logmap.erase(it))
		{
			const string& msg = it->second.msg;

			if (res + msg.length() > buffer.size()) break;

			memcpy(buffer.str() + res, msg.c_str(), msg.length());

			res += msg.length();
		}

		return res;
	}
	bool trySync()
	{
		int len = pop();

		CHECK_FALSE_RETURN(len > 0);

		return logfile.write(buffer.str(), len);
	}
	bool save(const string& msg, timeus now)
	{
		if (msg.length() <= 25 || msg.length() > buffer.size())
		{
			LogTrace(eERR, "remote log[%s] format error", msg.c_str());

			return false;
		}

		Logger log;
		string key = msg.substr(1, 25);

		log.msg = msg;
		log.ctime = now;

		for (int i = 0; i < 10; i++)
		{
			mtx.lock();

			if (logmap.size() < maxLogCnt)
			{
				logmap.insert(pair<string, Logger>(key, log));

				mtx.unlock();
	
				return true;
			}

			mtx.unlock();

			Sleep(100);
		}

		LogTrace(eERR, "save remote log[%s] failed", msg.c_str());

		return false;
	}
	static const sp<LoggerTimer>& Instance()
	{
		static sp<LoggerTimer> logger = newsp<LoggerTimer>();

		return logger;
	}
};

class LoggerUploader : public WorkItem
{
private:
	Mutex mtx;
	string link;
	HostItem loghost;
	queue<string> logqueue;

public:
	void run()
	{
		tryUpload();
	}
	bool init()
	{
		int port;
		string host;

		CHECK_FALSE_RETURN(yaml::config("log.host", host));
		CHECK_FALSE_RETURN(yaml::config("log.port", port));

		return setHost(host, port);
	}
	bool tryUpload()
	{
		JsonElement json;

		CHECK_FALSE_RETURN(link.length() > 0);
		CHECK_FALSE_RETURN(pop(json) > 0);

		SmartBuffer data = HttpHelper::GetResult(link, json.toString());

		if (data.isNull() && HttpHelper::GetLastStatus() == XG_SENDFAIL)
		{
			data = HttpHelper::GetResult(link, json.toString());
		}

		return data.isNull() ? false : true;
	}
	HostItem getHost()
	{
		Locker lk(mtx);

		return loghost;
	}
	int pop(JsonElement& json)
	{
		Locker lk(mtx);

		if (logqueue.empty()) return 0;

		int len = 0;
		int idx = 0;
		JsonElement list = json.addArray("list");

		while (logqueue.size() > 0 && len < 8 * 1024 * 1024)
		{
			const string& msg = logqueue.front();
			len += msg.length();
			list[idx++] = msg;
			logqueue.pop();
		}

		return idx;
	}
	bool push(const string& msg)
	{
		Locker lk(mtx);

		CHECK_FALSE_RETURN(logqueue.size() < maxLogCnt);

		logqueue.push(msg);

		return true;
	}
	bool setHost(const string& host, int port)
	{
		HostItem item;

		item.host = host;
		item.port = port;

		CHECK_FALSE_RETURN(item.canUse());

		Locker lk(mtx);

		if (loghost.canUse()) return false;

		link = HttpHelper::GetLink("tracemodule", host, port);
		std::swap(loghost, item);

		return true;
	}
	static const sp<LoggerUploader>& Instance()
	{
		static sp<LoggerUploader> logger = newsp<LoggerUploader>();

		return logger;
	}
};

static int GetLogHost(char* host, int* port)
{
	HostItem item = LoggerUploader::Instance()->getHost();

	if (item.host.empty()) return XG_NOTFOUND;

	strcpy(host, item.host.c_str());
	*port = item.port;

	return XG_OK;
}

static int SetLogHost(const char* host, int port)
{
	return LoggerUploader::Instance()->setHost(host, port) ? XG_OK : XG_FAIL;
}

static void LogCallback(const char* msg, int len)
{
	LoggerUploader::Instance()->push(string(msg, msg + len));
}

EXTERN_DLL_FUNC int HttpPluginInit(HttpServer* app, const char* path)
{
	Process::SetObject("HTTP_SET_LOG_HOST_FUNC", (void*)SetLogHost);
	Process::SetObject("HTTP_GET_LOG_HOST_FUNC", (void*)GetLogHost);

	LoggerTimer::Instance()->init();
	LoggerUploader::Instance()->init();
	stdx::timer(50, LoggerTimer::Instance());
	stdx::timer(500, LoggerUploader::Instance());
	HttpServer::Instance()->setLogCallback(LogCallback);

	return XG_OK;
}

int TraceModule::process()
{
	JsonElement data(request->getDataString());

	if (data.isObject())
	{
		auto now = GetTime();
		JsonElement list = data.get("list");

		for (JsonElement item : list)
		{
			LoggerTimer::Instance()->save(item.asString(), now);
		}
	}

	json["code"] = XG_OK;
	out << json;

	return XG_OK;
}